# Send all Python source files to a remote host over SFTP.
import connection_handler
import fnmatch
import getopt
import getpass
import os
import paramiko
import pickle
import sys

#paramiko.util.log_to_file('/tmp/paramiko_log.txt')

COMMANDS = None
SERVER_PATH = './private/transmission/data'

class Return:
	pass

def confirm(prompt=None, default_response=True):
	if prompt is None:
		prompt = 'Confirm'
	if default_response:
		prompt = '%s [%s]|%s:' % (prompt, 'y', 'n')
	else:
		prompt = '%s [%s]|%s:' % (prompt, 'n', 'y')
	while True:
		answer = raw_input(prompt)
		if not answer:
			return default_response
		if answer not in ('y', 'Y', 'n', 'N'):
			print 'Please enter y or n'
			continue
		return answer == 'y' or answer == 'Y'

def print_usage():
	template = '''Usage: python %s [-d|--debug] [-u uname |--username=uname] command'''
	print template % (sys.argv[0],)

def get_hostname_and_port_from_arg(host_str):
	split = host_str.split(':')
	hostname, port = None, None
	if len(split) == 2:
		# Both are specified
		hostname, port = split
		try:
			port = int(port)
		except ValueError:
			print 'Invalid port flag. Must be a number.'
			pass
	else:
		hostname = host_str
	return hostname, port

def process_args():
	ret = Return()
	args = sys.argv[1:]
	ret.interactive = len(args) == 0
	ret.args = args

def ask_questions(questions):
	answers = {}
	for question in questions:
		field, type = question.split('-')
		while True:
			answer = raw_input(field + '> ')
			if type == 'int':
				try:
					answers[field] = int(answer)
				except ValueError:
					continue
			else:
				answers[field] = answer
			break
	return answers

def show_configuration(context):
	get_configuration(verbose=True)
	return ContextResponseObject_EXIT_CONTEXT

def get_configuration(verbose=False):
	enctool_data = '~/.enctool/configuration.txt'
	
	enctool_data_path = os.path.expanduser(enctool_data)
	if not os.path.isfile(enctool_data_path):
		print 'File not found:', enctool_data_path
		return None
	f = open(enctool_data_path, 'r')
	answers = pickle.load(f)
	f.close()
	if verbose:
		for question in answers.keys():
			print '%s = %s (%s)' % (question, str(answers[question]), str(answers[question].__class__))
	return answers

def setup_configuration(context):
	enctool_dot_dir = '~/.enctool'
	enctool_data = '~/.enctool/configuration.txt'
	
	enctool_dot_dir_path = os.path.expanduser(enctool_dot_dir)
	enctool_data_path = os.path.expanduser(enctool_data)
	print 'Performing first run initialization...'
	if os.path.isdir(enctool_dot_dir_path):
		print 'Directory already exists at:', enctool_dot_dir_path
		print 'To reset enctool, re-run this command after removing or renaming this directory'
		return
	os.mkdir(enctool_dot_dir_path)
	questions = ('stuff',)
	questions = ('username-str', 'password-str', 'hostname-str', 'port-int')
	answers = ask_questions(questions)
	f = open(enctool_data_path, 'w')
	pickle.dump(answers, f)
	f.close()
	print 'Done with initialization'
	return ContextResponseObject_EXIT_CONTEXT

def setup_command(command, function):
	global COMMANDS
	if COMMANDS is None:
		COMMANDS = {}
	COMMANDS[command] = function

#def call_command(command):
#	try:
#		return COMMANDS[command]
#	except KeyError:
#		return None
#		# Should I be raising an exception?

def get_sftp_client():
	return connection_handler.get_handler(get_configuration()).sftp

def list_data(context, args=('*',)):
	sftp = get_sftp_client()
	if sftp is None:
		return ContextResponseObject_MAINTAIN_CONTEXT
	files = sftp.listdir(SERVER_PATH)
	try:
		filter = args[0]
		files = fnmatch.filter(files, filter)
	except IndexError:
		print 'Unfiltered'
		pass
	_print_file_list(files)
	print 'Listing complete'
#	sftp.close()
#	transport.close()
	print 'Starting another context now!'
	context = Context(ListDataConfiguration(files))
	return ContextResponseObject(context)

def _print_file_list(files):
	listing_hash = hash(tuple(files))
	print 'Listing hash:', hex(listing_hash)
	for i, file in enumerate(files):
		print '%d) %s' % (i+1, file)

def main():
	args = process_args()
	context = Context(ShellContextConfiguration())
	context.depth = 0
	contexts = [context]
	while len(contexts) != 0:
		current_context = contexts[-1]
		print 'Currently servicing context:', current_context
		response = current_context.fire()
		if response == ContextResponseObject_EXIT_CONTEXT:
			print 'Quitting'
			contexts.pop()
			continue
		if response.new_context is not None:
			response.new_context.depth = current_context.depth + 1
			contexts.append(response.new_context)
	print 'Out of contexts. Fallen out of while loop.'
	
#	print 'Initializing transport...'
#	transport = paramiko.Transport((args.hostname, args.port))
#	transport.connect(username=args.username, password=args.password)
#	sftp = paramiko.SFTPClient.from_transport(transport)
#	local_file = '~/example.txt'
#	sftp.put(os.path.expanduser(local_file), 'example.txt')
#	print 'Transfer complete'
#	sftp.close()
#	transport.close()

#	setup_command('setup', setup_configuration)
#	setup_command('display', show_configuration)
#	setup_command('list', list_data)
#	setup_command('interactive', interactive)

def report(obj):
	print [x for x in dir(obj) if x[:2] != '__' or x[-2:] != '__']
class ContextConfiguration:
	def __init__(self):
		print 'ContextConfiguration constructor!'
		self.motd = 'Default MOTD'
		self.commands = {}
		def handle(command_with_args):
			command_with_args_split = command_with_args.split()
			command, args = command_with_args_split[0].lower(), command_with_args_split[1:]
			if command == '?' or command == 'help':
				print 'Valid commands:', self.commands.keys()
				return ContextResponseObject_MAINTAIN_CONTEXT
			if command not in self.commands.keys():
				print 'Command not found:', command
				return ContextResponseObject_MAINTAIN_CONTEXT
			function = self.commands[command]
			response = None
			if len(args) == 0:
				response = function(self)
			else:
				response = function(self, args)
			return response
		self.process_command = handle
	
	def __repr__(self):
		return '(ContextConfiguration instance)'
		
class ShellContextConfiguration(ContextConfiguration):
	def __init__(self):
		ContextConfiguration.__init__(self)
		self.motd = 'You have entered interactive mode.'
		self.commands = {'setup':setup_configuration, 'display':show_configuration, 'list':list_data}
	
	def __repr__(self):
		return '(ShellContextConfiguration instance)'

class ListDataConfiguration(ContextConfiguration):
	def __init__(self, files):
		ContextConfiguration.__init__(self)
		self.motd = 'Now you can interact with these files on the server'
		self.commands = {'get':download_file, 'get-one':download_one_file, 'get_one':download_one_file, 'get1':download_one_file}
		self.files = files
	
	def __repr__(self):
		return '(ListDataConfiguration instance)'

def download_file(context, file_number):
	fn = int(file_number[0],)
	__download_file(context, fn)
	return ContextResponseObject_MAINTAIN_CONTEXT

def download_one_file(context, file_number):
	fn = int(file_number[0],)
	success = __download_file(context, fn)
	if success:
		return ContextResponseObject_EXIT_CONTEXT
	print 'Invalid index selected'
	return ContextResponseObject_MAINTAIN_CONTEXT
	# If the user does not enter a valid index for a file, stay on this context to give them another try

def __download_file(context, file_number):
	filename = None
	try:
		filename = context.files[file_number - 1]
	except IndexError:
		return False
	confirmation = confirm("Download file '%s'" % (filename,))
	if confirmation:
		sftp = get_sftp_client()
		if sftp is None:
			return False
		### NOTE: Start here next time
		full_filename = SERVER_PATH + '/' + filename
		## Pseudo-code follows:
		# Issue command to server to encrypt file found at full_filename and place it at full_filename.gpg, for example
		# (note this may entail tarring the file or otherwise making it into a single file if it is a directory)
		# Use standard ftp to download the encrypted file
		files = sftp.listdir('./private/transmission/data')
	else:
		return True
	return True

class Context:
	def __init__(self, configuration):
		self.configuration = configuration
		self.first_time_fired = True
		self.depth = 'Default'
	
	def fire(self):
		print 'Firing...'
		if self.first_time_fired:
			print self.configuration.motd
			self.first_time_fired = False
		input = raw_input(self.prompt())
		input = input.strip().lower()
		if input == '':
			return ContextResponseObject_MAINTAIN_CONTEXT
		if input == 'quit':
			return ContextResponseObject_EXIT_CONTEXT
		### Remember this is here ###
		if input == 'die':
			print 'Ignoring other contexts, quitting immediately'
			sys.exit(0)
		#############################
		response = self.configuration.process_command(input)
		if response is None:
			print str(self.configuration.__class__), 'is uncooperative'
			print 'Uh oh!!'
			sys.exit(3)
		if response.new_context is not None:
			print 'Switching to new context:', response.new_context
		return response
	
	def prompt(self):
		return '>' * (self.depth + 1) + ' '
		
	def __repr__(self):
		return repr(self.configuration)

class ContextResponseObject:
	def __init__(self, new_context):
		self.new_context = new_context
ContextResponseObject_EXIT_CONTEXT = ContextResponseObject(None)
ContextResponseObject_MAINTAIN_CONTEXT = ContextResponseObject(None)
	
if __name__ == '__main__':
	main()
